package nayasis.common.log;

import java.util.List;
import java.util.StringTokenizer;

import nayasis.common.util.StringUtil;

/**
 * 로그유틸
 *
 * @author 정화수
 * @since  2013.01.14
 *
 */
public final class LoggerModule {

	/**
	 * Throwable 용인지를 확인하기 위한 flag
	 */
	private static final String FLAG_THROWABLE = "[NAYASIS_THROWABLE]";

	/**
	 * Self Class Name
	 */
	private final String thisClassName = this.getClass().getName();

	/**
	 * 로그 활성화여부
	 */
	private boolean isEnable = true;

	/**
	 * Logger 이름
	 */
	private String name = "";

	/**
	 * 생성자
	 *
	 * @param name   Logger 이름
	 * @param enable 로그 활성화여부
	 */
	public LoggerModule( String name, boolean enable ) {
		this.name = name;
		setEnable( enable );
	}

	/**
	 * 로거 활성화 여부를 확인한다.
	 *
	 * @return 활성화여부
	 */
	public boolean isEnable() {
		return this.isEnable;
	}

	/**
	 * 로거 활성화 여부를 변경한다.
	 *
	 * @param enable 로거 활성화여부
	 */
	public void setEnable( boolean enable ) {
		this.isEnable = enable;
	}

	/**
     * Array 출력용 메세지를 구한다.
     *
     * @param array 출력할 배열
     */
    private String getArrayString( List<?> array ) {

	    int arrayCnt = array.size();
	    
	    if( arrayCnt == 0 ) return "";

	    StringBuffer result = new StringBuffer();

	    for( int i = 0; i < arrayCnt; i++ ) {

	        Object row = array.get( i );
	        
	        result.append( "------------------------------------------" );
	        result.append( "Index : " ).append( i );
	        result.append( "------------------------------------------" );
	        result.append( row );

	    }
	    
	    result.append( "------------------------------------------" );

	    return result.toString();

	}


	/**
	 * 메세지를 출력한다.
	 *
	 * @param message 출력할 메세지
	 */
	public void println( Object message ) {

		if( ! isEnable() ) return;

		StringBuffer sb = new StringBuffer();
		boolean isThrowable = false;

		String logHeader = getLogHeader();
		String logText   = "";

		if( message == null ) {
		    logText = "null";
		} else if( message instanceof List ) {
		    logText = getArrayString( (List<?>) message );
		} else {
		    logText = message.toString();
		}

		// 문자열에 에러용 플래그가 포함되어 있는지 여부를 확인
		if( logText.length() >= FLAG_THROWABLE.length() &&
			FLAG_THROWABLE.equals(logText.substring(0,FLAG_THROWABLE.length())) ) {

			isThrowable = true;
			logText = logText.substring( FLAG_THROWABLE.length() );

		}

		// 로그용 문자열로 조합
		StringTokenizer tokenizer = new StringTokenizer(logText, "\n" );

		while( tokenizer.hasMoreElements() ) {
			sb.append( logHeader ).append( tokenizer.nextToken() ).append( "\r\n" );
		}

		// 로그 출력
		printAtConsole( sb.toString(), isThrowable );

	}

	/**
	 * 메세지를 출력한다.
	 *
	 * @param messageCode 출력할 메세지 혹은 메세지코드
	 * @param param       '@' 문자를 치환할 파라미터
	 */
	public void println( String messageCode, Object... param ) {

		if( ! isEnable() ) return;

		String logText = StringUtil.format( messageCode, param );

		println( logText );

	}

	/**
	 * Exception을 출력한다.
	 *
	 * @param exception Exception 객체
	 */
	public void println( Throwable exception ) {

		if( ! isEnable() ) return;

		StringBuffer sb = new StringBuffer();
		sb.append( FLAG_THROWABLE );
		sb.append( StringUtil.getStackTrace(exception) );

		println( sb );

	}

	/**
	 * 로그내용을 출력한다.
	 *
	 * @param text 출력할 텍스트
	 * @param isThrowable 에러용으로 찍을지 여부
	 */
	private void printAtConsole( String text, boolean isThrowable ) {

		if( isThrowable ) {
			System.err.print( text );
		} else {
			System.out.print( text );
		}

	}

	/**
	 * 로그 한 줄에 기록할 맨 앞단 헤더내용을 생성한다.
	 *
	 * @return [YYYY-MM-DD HI:MM:SS / 클래스명.메서드명(파일명:라인번호) ] 형식의 문자열
	 */
	private String getLogHeader() {

		StringBuffer sb = new StringBuffer();

		sb.append( '[' );
//		sb.append( new NDate().toString("YYYY-MM-DD HH:MI:SS") );
//		sb.append( " / " );
		sb.append( name );
		sb.append( " " );
		sb.append( getSourceInfo() );
		sb.append( ']' );
		sb.append( ' ' );

		return sb.toString();

	}

	/**
	 * 로그가 호출된 지점의 StackTrace 정보를 가져온다.
	 *
	 * @return StackTrace 정보
	 */
	private StackTraceElement getStackTrace() {

		StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

		StackTraceElement stackForLogger = null;

		boolean watchStart = false;

		for( int i = 0, iCnt = stackTrace.length; i < iCnt; i++ ) {

			StackTraceElement stack = stackTrace[ i ];

			if( watchStart == false && ! thisClassName.equals(stack.getClassName()) ) {
				continue;

			} else if( watchStart == true && ! thisClassName.equals(stack.getClassName()) ) {
				stackForLogger = stack;
				break;

			} else {
				watchStart = true;
			}

		}

		return stackForLogger;

	}

	/**
	 * StackTrace 정보에서 소스 정보를 뽑아온다.
	 *
	 * @return 소스정보
	 */
	private StringBuffer getSourceInfo() {

		StringBuffer sb = new StringBuffer();

		StackTraceElement stackTrace = getStackTrace();

//		sb.append( getClassName(stackTrace) );

		String fileName   = stackTrace.getFileName();
		int    lineNumber = stackTrace.getLineNumber();

		if( fileName != null && lineNumber >= 0 ) {
			sb.append( '(' );
			sb.append( fileName ).append( ':' ).append( lineNumber );
			sb.append( ')' );
		}

		return sb;

	}

	/**
	 * StackTrace 정보에서 클래스 정보를 뽑아온다.
	 *
	 * @return 클래스정보
	 */
	@SuppressWarnings("unused")
    private StringBuffer getClassName( StackTraceElement stackTrace ) {

		StringBuffer sb = new StringBuffer();

		String className  = stackTrace.getClassName();
		String methodName = stackTrace.getMethodName();

		if( stackTrace.isNativeMethod() ) {
			sb.append( className );
			sb.append( '.' );
			sb.append( methodName );
			sb.append( "(Native Method)" );
		} else {
			sb.append( className.substring(className.lastIndexOf('.') + 1) );
			sb.append( '.' );
			sb.append( methodName );
		}

		return sb;

	}

}